package com.kinyx.framework.utils;

import java.util.Calendar;
import java.util.Date;

import com.kinyx.framework.Configs;
import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.enums.impl.ErrorCode0;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.exception.KRuntimeException;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 日历工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KCalendarUtils {

	@ReturnNull("date == null")
	private static Calendar calendar(@CanNull final Date date) {
		if (date == null) { return null; }
		final Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c;
	}

	@ReturnNull("dateStr == null")
	private static Date date(@CanNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern) {
		if (dateStr == null) { return null; }
		if (pattern == null) { return date(dateStr, Configs.default_KCalendarUtils_datePattern); }
		return KDateTimeUtils.toDate(dateStr, pattern);
	}

	/** 默认工作日 */
	private static final int[] WORK_DAYS = new int[]{
			Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY
	};

	// ===================================================== isWorkDay ===================================================== //
	/**
	 * 判断日期是否为工作日
	 * @param date 日期对象
	 * @param workdays 工作日数组
	 * @return true：工作日；false：休息日；
	 */
	@ReturnNotNull
	public static boolean isWorkDay(@CanNotNull final Date date, @CanNull(defaultValue = "KCalendarUtils.WORK_DAYS") final int... workdays) {
		if (KArrayUtils.isEmpty(workdays)) { return isWorkDay(date, WORK_DAYS); }
		final Calendar c = calendar(date);
		final int dayofWeek = c.get(Calendar.DAY_OF_WEEK);
		for (final int workday : workdays) { if (dayofWeek == workday) { return true; } }
		return false;
	}

	/**
	 * 判断日期是否为工作日
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @param workdays 工作日数组
	 * @return true：工作日；false：休息日；
	 */
	@ReturnNotNull
	public static boolean isWorkDay(@CanNotNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern, @CanNull(defaultValue = "KCalendarUtils.WORK_DAYS") final int... workdays) {
		return isWorkDay(date(dateStr, pattern), workdays);
	}

	/**
	 * 判断日期是否为工作日
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @param workdays 工作日数组
	 * @return true：工作日；false：休息日；
	 */
	@ReturnNotNull
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static boolean isWorkDay(@CanNotNull final String dateStr, @CanNull(defaultValue = "KCalendarUtils.WORK_DAYS") final int... workdays) {
		return isWorkDay(dateStr, null, workdays);
	}

	// ===================================================== isOffDay ===================================================== //
	/**
	 * 判断日期是否为休息日
	 * @param date 日期对象
	 * @param workdays 工作日数组
	 * @return true：休息日；false：工作日；
	 */
	@ReturnNotNull
	public static boolean isOffDay(@CanNotNull final Date date, @CanNull(defaultValue = "KCalendarUtils.WORK_DAYS") final int... workdays) {
		return !isWorkDay(date, workdays);
	}

	/**
	 * 判断日期是否为休息日
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @param workdays 工作日数组
	 * @return true：休息日；false：工作日；
	 */
	@ReturnNotNull
	public static boolean isOffDay(@CanNotNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern, @CanNull(defaultValue = "KCalendarUtils.WORK_DAYS") final int... workdays) {
		return isOffDay(date(dateStr, pattern), workdays);
	}

	/**
	 * 判断日期是否为休息日
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @param workdays 工作日数组
	 * @return true：休息日；false：工作日；
	 */
	@ReturnNotNull
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static boolean isOffDay(@CanNotNull final String dateStr, @CanNull(defaultValue = "KCalendarUtils.WORK_DAYS") final int... workdays) {
		return isOffDay(dateStr, null, workdays);
	}

	// ===================================================== getFirstDayOfWeek ===================================================== //
	/**
	 * 取周的第一天
	 * @param date 日期对象
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周的第一天
	 */
	@ReturnNull("date == null")
	public static Date getFirstDayOfWeek(@CanNull final Date date, @CanNotNull final int firstDayOfWeek) {
		if (date == null) { return null; }
		final Calendar c = calendar(date);
		c.setFirstDayOfWeek(firstDayOfWeek);
		c.set(Calendar.DAY_OF_WEEK, firstDayOfWeek);
		c.set(Calendar.HOUR, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
		return c.getTime();
	}

	/**
	 * 取周的第一天
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周的第一天
	 */
	@ReturnNull("dateStr == null")
	public static Date getFirstDayOfWeek(@CanNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern, @CanNotNull final int firstDayOfWeek) {
		if (dateStr == null) { return null; }
		return getFirstDayOfWeek(date(dateStr, pattern), firstDayOfWeek);
	}

	/**
	 * 取周的第一天
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周的第一天
	 */
	@ReturnNull("dateStr == null")
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static Date getFirstDayOfWeek(@CanNull final String dateStr, @CanNotNull final int firstDayOfWeek) {
		return getFirstDayOfWeek(dateStr, null, firstDayOfWeek);
	}

	// ===================================================== getLastDayOfWeek ===================================================== //
	/**
	 * 取周的最后一天
	 * @param date 日期对象
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周的最后一天
	 */
	@ReturnNull("date == null")
	public static Date getLastDayOfWeek(@CanNull final Date date, @CanNotNull final int firstDayOfWeek) {
		if (date == null) { return null; }
		final Calendar c = calendar(date);
		c.setFirstDayOfWeek(firstDayOfWeek);
		c.set(Calendar.DAY_OF_WEEK, firstDayOfWeek + 6);
		c.set(Calendar.HOUR, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
		return c.getTime();
	}

	/**
	 * 取周的最后一天
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周的最后一天
	 */
	@ReturnNull("dateStr == null")
	public static Date getLastDayOfWeek(@CanNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern, @CanNotNull final int firstDayOfWeek) {
		if (dateStr == null) { return null; }
		return getLastDayOfWeek(date(dateStr, pattern), firstDayOfWeek);
	}

	/**
	 * 取周的最后一天
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周的最后一天
	 */
	@ReturnNull("dateStr == null")
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static Date getLastDayOfWeek(@CanNull final String dateStr, @CanNotNull final int firstDayOfWeek) {
		return getLastDayOfWeek(dateStr, null, firstDayOfWeek);
	}

	// ===================================================== getFirstDayOfMonth ===================================================== //
	/**
	 * 取月的第一天
	 * @param date 日期对象
	 * @return 月的第一天
	 */
	@ReturnNull("date == null")
	public static Date getFirstDayOfMonth(@CanNull final Date date) {
		if (date == null) { return null; }
		final Calendar c = calendar(date);
		c.set(Calendar.DATE, 1);
		c.set(Calendar.HOUR, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
		return c.getTime();
	}

	/**
	 * 取月的第一天
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @return 月的第一天
	 */
	@ReturnNull("dateStr == null")
	public static Date getFirstDayOfMonth(@CanNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern) {
		if (dateStr == null) { return null; }
		return getFirstDayOfMonth(date(dateStr, pattern));
	}

	/**
	 * 取月的第一天
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @return 月的第一天
	 */
	@ReturnNull("dateStr == null")
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static Date getFirstDayOfMonth(@CanNull final String dateStr) {
		return getFirstDayOfMonth(dateStr, null);
	}

	// ===================================================== getLastDayOfMonth ===================================================== //
	/**
	 * 取月的最后一天
	 * @param date 日期对象
	 * @return 月的最后一天
	 */
	@ReturnNull("date == null")
	public static Date getLastDayOfMonth(@CanNull final Date date) {
		if (date == null) { return null; }
		final Calendar c = calendar(date);
		c.set(Calendar.DATE, 1);
		c.roll(Calendar.DATE, -1);
		c.set(Calendar.HOUR, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
		return c.getTime();
	}

	/**
	 * 取月的最后一天
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @return 月的最后一天
	 */
	@ReturnNull("dateStr == null")
	public static Date getLastDayOfMonth(@CanNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern) {
		if (dateStr == null) { return null; }
		return getLastDayOfMonth(date(dateStr, pattern));
	}

	/**
	 * 取月的最后一天
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @return 月的最后一天
	 */
	@ReturnNull("dateStr == null")
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static Date getLastDayOfMonth(@CanNull final String dateStr) {
		return getLastDayOfMonth(dateStr, null);
	}

	// ===================================================== getWeekOfYear ===================================================== //
	/**
	 * 取当前周在年内是第多少周
	 * @param date 日期对象
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周值
	 */
	@ReturnNotNull
	public static int getWeekOfYear(@CanNotNull final Date date, @CanNotNull final int firstDayOfWeek) {
		final Calendar c = calendar(date);
		c.setFirstDayOfWeek(firstDayOfWeek);
		return c.get(Calendar.WEEK_OF_YEAR);
	}

	/**
	 * 取当前周在年内是第多少周
	 * @param dateStr 日期时间字符串
	 * @param pattern 日期格式模板
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周值
	 */
	@ReturnNotNull
	public static int getWeekOfYear(@CanNotNull final String dateStr, @CanNull(defaultValue = "Configs.default_KCalendarUtils_datePattern") final String pattern, @CanNotNull final int firstDayOfWeek) {
		return getWeekOfYear(date(dateStr, pattern), firstDayOfWeek);
	}

	/**
	 * 取当前周在年内是第多少周
	 * @param dateStr 日期时间字符串（yyyyMMdd）
	 * @param firstDayOfWeek 周的第一天的值
	 * @return 周值
	 */
	@ReturnNotNull
	@Tester("com.kinyx.framework.utils.KCalendarUtils_Tester_")
	public static int getWeekOfYear(@CanNotNull final String dateStr, @CanNotNull final int firstDayOfWeek) {
		return getWeekOfYear(dateStr, null, firstDayOfWeek);
	}

	// ===================================================== getQuarterByMonth ===================================================== //
	/** 春 */
	public final static int SPRING = 1;
	/** 夏 */
	public final static int SUMMER = 2;
	/** 秋 */
	public final static int AUTUMN = 3;
	/** 冬 */
	public final static int WINTER = 4;

	/**
	 * 月份所属季度
	 * @param month 月份
	 * @return 非null
	 * @see #SPRING
	 * @see #SUMMER
	 * @see #AUTUMN
	 * @see #WINTER
	 */
	@ReturnNotNull
	@Throws(at = "KCalendarUtils.getQuarterByMonth(int)", value = "ErrorCode0.ValueLessThenMinValue", when = "month < Calendar.JANUARY")
	@Throws(at = "KCalendarUtils.getQuarterByMonth(int)", value = "ErrorCode0.ValueMoreThenMaxValue", when = "month > Calendar.DECEMBER")
	public static int getQuarterByMonth(@CanNotNull final int month) {
		if (month < Calendar.JANUARY) {
			throw new KRuntimeException(ErrorCode0.ValueLessThenMinValue).param("value", month).param("minValue", Calendar.JANUARY);
		}
		if (month > Calendar.DECEMBER) {
			throw new KRuntimeException(ErrorCode0.ValueMoreThenMaxValue).param("value", month).param("maxValue", Calendar.DECEMBER);
		}
		switch (month) {
			case Calendar.JANUARY:
			case Calendar.FEBRUARY:
			case Calendar.MARCH:
				return SPRING;
			case Calendar.APRIL:
			case Calendar.MAY:
			case Calendar.JUNE:
				return SUMMER;
			case Calendar.JULY:
			case Calendar.AUGUST:
			case Calendar.SEPTEMBER:
				return AUTUMN;
			case Calendar.OCTOBER:
			case Calendar.NOVEMBER:
			case Calendar.DECEMBER:
				return WINTER;
			default :
				throw new KRuntimeException(ErrorCodeX.NeverHappens);
		}
	}

}
